package com.mydemo.spring.dynamicroutes.support;

import org.omg.CORBA.SystemException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.event.WeightDefinedEvent;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.support.WeightConfig;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import reactor.core.publisher.Flux;

import java.util.*;

import static java.util.Collections.synchronizedMap;

/**
 * @name 路由器
 * @auth DIY
 * @date 2020/10/13 17:58
 **/
public class MyRouteLocator implements Ordered, RouteLocator, ApplicationEventPublisherAware {

    private static final Logger log = LoggerFactory.getLogger(MyRouteLocator.class);

    /**
     * 路由权重配置
     */
    public static final String ROUTE_WEIGHT_CONFIG = "route_weight_confg";


    private final Map<String, Route> cacheRoutes = synchronizedMap(new LinkedHashMap<>());
    private Flux<Route> workRoutes;
    private ApplicationEventPublisher applicationEventPublisher;

    public static MyRouteLocator build(Flux<Route> newRoutes){
        MyRouteLocator newInstance = new MyRouteLocator();
        newRoutes.subscribe(route -> {
            newInstance.cacheRoutes.put(route.getId(), route);
        });
        newInstance.fetch();
        return newInstance;
    }

    private MyRouteLocator() {}

    private Flux<Route> fetch() {
        workRoutes = Flux.fromIterable(cacheRoutes.values()).sort(Comparator.comparing(route -> route.getOrder()));
        return workRoutes;
    }

    @Override
    public Flux<Route> getRoutes() {
        return workRoutes;
    }

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.applicationEventPublisher = applicationEventPublisher;
    }

    @Override
    public int getOrder() {
        return 0;
    }


    public List<WeightConfig> add(Flux<Route> newRoutes) {
        List<WeightConfig> routeConfigs = new ArrayList<>();
        newRoutes.subscribe(route -> {
            cacheRoutes.put(route.getId(), route);
            WeightConfig config = getRouteWeightConfig(route);
            routeConfigs.add(config);
            log.warn("网关路由服务{}准备上线", config);
        }, throwable -> {
            throw new RuntimeException("路由加载失败", throwable);
        });
        fetch();
        publishRefreshRouteEvent();
        return routeConfigs;
    }

    public WeightConfig del(String routeId) {
        if (cacheRoutes.containsKey(routeId)) {
            Route route = cacheRoutes.get(routeId);
            WeightConfig config = getRouteWeightConfig(route);
            config.setWeight(0);
            publishRefreshRouteWeightEvent(config);
            cacheRoutes.remove(routeId);
            fetch();
            publishRefreshRouteEvent();
            log.warn("网关路由服务[{}]已下线", routeId);
            return config;
        }
        throw new RuntimeException("Route not found: " + routeId);
    }

    public static WeightConfig getRouteWeightConfig(Route route){
        Object o = route.getMetadata().get(ROUTE_WEIGHT_CONFIG);
        if (null == o){
            throw new RuntimeException("当前路由无权重配置信息");
        }
        return (WeightConfig)o;
    }

    private void publishRefreshRouteEvent(){
        applicationEventPublisher.publishEvent(new RefreshRoutesEvent(this));

    }

    private void publishRefreshRouteWeightEvent(WeightConfig weightConfig){
        applicationEventPublisher.publishEvent(new WeightDefinedEvent(this, weightConfig));
    }
}
